package robocup.component.situations;

import java.util.Arrays;

import robocup.component.DAInamiteConstants;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.KickAction;
import robocup.component.actions.TackleAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * 
 * The {@link PassEvaluator} tries to find out if a pass to a given point
 * succeeds without the oppponent intercepting the ball before. More than that
 * it optimizes the result trying to pass through opponents and keep the ball
 * trajectory as far away from interception as possible. By now this is only
 * used for the {@link ScoreSituation} when trying to find the best way for
 * scoring.<br>
 */
public class PassEvaluator {

  public static double EMPTY_VALUE         = Double.MAX_VALUE;

  private Movements    movements;

  private Player[]     opponents;

  private final int          maxCycle            = DAInamiteConstants.PROPHET_MAX_CYCLES_PASSING;

  /**
   * true if an opponent is near enough to be considered
   */
  private final boolean[]    relevantOpponents   = new boolean[11];

  /**
   * time need by opponents for reacting when passing
   */
  private int[]        playerOffset        = new int[11];

  private final Player       passingPlayer       = Player.getDummyInstance();

  private final Ball         ball                = Ball.getDummyInstance();

  private final Ball         dummyBall           = Ball.getDummyInstance();

  // the point to pass to
  private final Vektor       targetPoint         = new Vektor();

  /**
   * the max turn angles for all opponents turning 1x or 2x.
   */
  private final double[][]   maxTurnAngle        = new double[11][2];

  /**
   * true if a player is intercepting from the left side
   */
  private final boolean[]    isInterceptingLeft  = new boolean[11];

  /**
   * true if a player is intercepting from the right side
   */
  private final boolean[]    isInterceptingRight = new boolean[11];

  // max pass deviation to the right (i.e. right goal post)
  private double       initAngleRight;

  // max pass deviation to the right (i.e. left goal post)
  private double       initAngleLeft;

  /**
   * A class that is responsible for evaluating the results of different
   * simulated passes (effectiveness).
   * 
   * @param movements -
   *          the {@link Movements}
   * @param opponents -
   *          an array with all opponent players
   */
  public PassEvaluator(
      final Movements movements,
      final Player[] opponents) {

    this.movements = movements;
    this.opponents = opponents;
    Arrays.fill(this.relevantOpponents, true);
  }

  /**
   * set the parameters.
   * 
   * @param me -
   *          self
   * @param ball -
   *          ball
   * @param targetPoint -
   *          point to pass to
   * @param playerOffset -
   *          offset of opponent players (cycles needed for reacting)
   * @param opponents -
   *          opponents
   * @param relevantOpponents -
   *          relevant opponents are true
   * @param maxAngleLeft -
   *          max pass deviation to the left (i.e. angle from pass line to left
   *          goal post)
   * @param maxAngleRight -
   *          max pass deviation to the right (i.e. angle from pass line to
   *          right goal post)
   */
  public void setParams(final Player me,
      final Ball ball,
      final Vektor targetPoint,
      final int[] playerOffset,
      final Player[] opponents,
      final boolean[] relevantOpponents,
      final double maxAngleLeft,
      final double maxAngleRight) {

    this.passingPlayer.copy(me);
    this.ball.copy(ball);
    this.targetPoint.copy(targetPoint);
    this.opponents = opponents;
    for (int i = 0; i < 11; i++) {
      this.relevantOpponents[i] = relevantOpponents[i];
    }
    this.playerOffset = playerOffset;
    this.initAngleLeft = maxAngleLeft;
    this.initAngleRight = maxAngleRight;
    this.reset();
  }

  /**
   * reset.
   * 
   */
  private void reset() {

    Arrays.fill(this.isInterceptingLeft, false);
    Arrays.fill(this.isInterceptingRight, false);
  }

  /**
   * main method which determines the pass results.
   * 
   * @param results -
   *          results - the values of this object will be overwritten!!
   * @return results - (same object as in param)
   */
  public PassEvaluatorResults evaluate(final PassEvaluatorResults results) {

    results.reset();
    results.setInitAngleLeft(this.initAngleLeft);
    results.setInitRightAngle(this.initAngleRight);

//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("\nScoreSit (eval shot) "+passingPlayer.getCycle()+" P:"+passingPlayer.getNumber()+"\n");
//    }

    final double distanceToTarget = this.ball.getDistance(this.targetPoint);
    final double angleToTarget = this.ball.getAngleTo(this.targetPoint);
//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("dist2Target: "+distanceToTarget);
//      System.out.println("ang2Target: "+angleToTarget);
//    }

    // filtering distant players (simple version)
    for (int playerIndex = 0; playerIndex < this.opponents.length; playerIndex++) {
//      if (ScoreSituation.debugThis[5]) {
//        System.out.println("checking player: "+playerIndex);
//      }
      if (!this.relevantOpponents[playerIndex]) {
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("not relevant");
//        }
        continue;
      }
      else if (this.opponents[playerIndex].getDistance(this.ball) > 2 * distanceToTarget) {
        this.relevantOpponents[playerIndex] = false;
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("too far from ball");
//        }
        continue;
      }
      else if (this.opponents[playerIndex].getDistance(this.targetPoint) > distanceToTarget) {
        this.relevantOpponents[playerIndex] = false;
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("too far from target");
//        }
        continue;
      }
      else {
        if (Vektor.normalize(angleToTarget - this.ball
            .getAngleTo(this.opponents[playerIndex])) > 0) {
          results.setIsLeft(playerIndex, true);
//          if (ScoreSituation.debugThis[5]) {
//            System.out.println("left player");
//          }
        }
        else {
          results.setIsLeft(playerIndex, false);
//          if (ScoreSituation.debugThis[5]) {
//            System.out.println("right player");
//          }
        }
      }
    }

    // determine the kick action
    final KickAction kickToPoint = BasicActionFactory.kickFastest(this.passingPlayer,
        this.ball, this.targetPoint);
//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("\nthe kick: "+kickToPoint);
//    }
    
    // TODO: check, if this works correct
    TackleAction tackle= new TackleAction(0);
    double tackleAngle = ball.getAngleTo(ball.predictBallAfterAction(this.passingPlayer, tackle).getPosition());
    if (tackleAngle < this.initAngleLeft || tackleAngle > this.initAngleRight) {
      tackle = new TackleAction(180);
      tackleAngle = ball.getAngleTo(ball.predictBallAfterAction(this.passingPlayer, tackle).getPosition());
      if (tackleAngle < this.initAngleLeft || tackleAngle > this.initAngleRight) {
        tackle = null;
      }      
    }
    // TODO ScoSi add tackling here
    if (kickToPoint == null) {
//      if (ScoreSituation.debugThis[5]) {
//        System.out.println("\nkick null --> no shot");
//      }
      results.setNoPassing(true);
      return results;
    }

    // the max turn angle for all opponents (used by movements.*)
    this.setMaxTurnAngle();

    // dummy = ball after kick
    this.dummyBall.copy(this.ball);
    this.dummyBall.predictThisAfterAction(this.passingPlayer, kickToPoint);
//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("\nball: "+ball.getPosition());
//      System.out.println("ball after kick: "+dummyBall.getPosition());
//    }

    int correctedCycle;
    double angle;
    double distance;

    // traveled ball way length
    double ballWayLength = ball.getDistance(dummyBall);
//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("\ninitial ballWayLength: "+ballWayLength);
//    }

    // simulate the kicked ball till it reaches the target and try if
    // opponents can intercept save the interception data to "results"
    for (int cycle = 1; cycle < this.maxCycle; cycle++) {
//      if (ScoreSituation.debugThis[5]) {
//        System.out.println("\nsimulate cycle: "+cycle);
//      }

      // no speed, no fun
      if (ballWayLength == 0) {
        break;
      }

      // test for all relevant opponents if someone gets the simulated
      // ball
      for (int playerIndex = 0; playerIndex < this.opponents.length; playerIndex++) {
        if (!this.relevantOpponents[playerIndex]) {
          continue;
        }
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("check player: "+playerIndex);
//        }
        correctedCycle = Math.min(cycle + this.playerOffset[playerIndex],
            this.maxCycle - 1);

        // assuming that the goalie will go to the short post first
//      TODO ScoSi overthink this... if short is save?!?
//      TODO ScoSi maybe use min?
//        if (this.opponents[playerIndex].isGoalie() && this.ball.getPosition().x * this.targetPoint.x < 0) {
//          correctedCycle -= 2;
//        }

//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("corr cycler: "+correctedCycle);
//        }

        // opponent may be tackle dead
        if (correctedCycle < 0) {
          continue;
        }

        distance = this.movements
            .getLeftDistance(
                playerIndex + 11 - 1,
                correctedCycle,
                this.dummyBall.getPosition(),
                this.opponents[playerIndex].getFuturePos(cycle, this.opponents[playerIndex].getPConf().PLAYER_DECAY),
                this.opponents[playerIndex].getBodyDir(),
                this.maxTurnAngle[playerIndex][0],
                this.maxTurnAngle[playerIndex][1],
                this.opponents[playerIndex].isGoalie() ? this.opponents[playerIndex]
                    .getCatchDistance() : this.opponents[playerIndex]
                    .getKickDistance(), 0.95);

        final double ang1= dummyBall.getAngleTo(opponents[playerIndex].getPosition());
        final double ang2= ball.getPosition().getAngleTo(dummyBall.getPosition());
        double normAngle= Vektor.normalize(ang1 - ang2);
        if (Math.signum(distance) != Math.signum(normAngle)) {
          normAngle= -normAngle;
        }
        double normDist= Math.sin(Math.toRadians(normAngle))*distance;
        if (Math.signum(distance) != Math.signum(normDist)) {
          normDist= -normDist;
        }
        final double normBallWay= Math.cos(Math.toRadians(normAngle))*distance;
        angle = Math.toDegrees(Math.atan(normDist / (ballWayLength+normBallWay)));
        if (Math.signum(angle) != Math.signum(distance)) {
          angle= -angle;
        }
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("\n angle calc\n");
//          System.out.println("dist: "+distance);
//          System.out.println("ballWay: "+ballWayLength);
//          System.out.println("dummyBall -> opp: "+ang1);
//          System.out.println("ball --> dummyBall: "+ang2);
//          System.out.println("normAngle: "+normAngle);
//          System.out.println("normDist: "+normDist);
//          System.out.println("normBallWay: "+normBallWay);;
//        }

//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("distance: "+distance);
//          System.out.println("angle: "+angle);
//        }

        // save results
        results.setAngle(playerIndex, cycle, angle);
        results.setDistance(playerIndex, distance);
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("new results: "+results);
//        }
        if (results.isFinished()) {
          return results;
        }
      }

      ballWayLength += this.dummyBall.getSpeed().getLength();
//      if (ScoreSituation.debugThis[5]) {
//        System.out.println("ballWayLength: "+ballWayLength);
//      }

      this.dummyBall.extrapolate(1);
      if (ballWayLength > distanceToTarget) {
//        if (ScoreSituation.debugThis[5]) {
//          System.out.println("ball made its way --> break");
//        }
        break;
      }
    }
//    if (ScoreSituation.debugThis[5]) {
//      System.out.println("complete results: "+results);
//    }
    return results;
  }

  /**
   * set the max turn angles for every player based on the players speed. (used
   * for efficiency)
   * 
   */
  private void setMaxTurnAngle() {

    for (final Player p : this.opponents) {
      this.maxTurnAngle[p.getNumber() - 1][0] = p.getMaxTurningAngle1();
      this.maxTurnAngle[p.getNumber() - 1][1] = p.getMaxTurningAngle2();
    }
  }
}
